# YunCha-Coffee PyCharm
# products 86155
# 2025/8/31 10:13
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Query
from typing import List, Optional

from pydantic import BaseModel,Field,validator
from decimal import Decimal


from sqlalchemy import func, and_, or_, select
from sqlalchemy.testing import db
from tortoise.expressions import Q

from app.dependencies import require_admin
from app.models import CoffeeProduct, MerchandiseProduct, User, CoffeeCategory, MerchandiseCategory
from app.utils.file_handlers import save_upload_file, delete_file

coffeeProduct_api = APIRouter(prefix="/coffeeProduct")
merchandiseProduct_api = APIRouter(prefix="/merchandiseProduct")


# 产品后端代码中 coffeeProduct_api 路由组内
@coffeeProduct_api.get("/stats", summary="咖啡商品统计", dependencies=[Depends(require_admin)])
async def coffee_stats():
    """获取咖啡商品统计数据（总数量/上架数量/推荐数量）"""
    try:
        # 总数量
        total = await CoffeeProduct.all().count()
        # 上架数量（咖啡用 is_puton 字段）
        active = await CoffeeProduct.filter(is_puton=True).count()
        # 推荐数量（咖啡用 is_recommend 字段）
        recommended = await CoffeeProduct.filter(is_recommend=True).count()

        return {
            "code": 200,
            "message": "咖啡统计成功",
            "data": {
                "total": total,
                "active": active,
                "recommended": recommended
            }
        }
    except Exception as e:
        print(f"咖啡统计错误: {str(e)}")
        return {
            "code": 500,
            "message": f"咖啡统计失败: {str(e)}",
            "data": {}
        }


# 产品后端代码中 merchandiseProduct_api 路由组内
@merchandiseProduct_api.get("/stats", summary="周边商品统计", dependencies=[Depends(require_admin)])
async def merchandise_stats():
    """获取周边商品统计数据（总数量/上架数量）"""
    try:
        # 总数量
        total = await MerchandiseProduct.all().count()
        # 上架数量（周边用 is_active 字段）
        active = await MerchandiseProduct.filter(is_active=True).count()

        return {
            "code": 200,
            "message": "周边统计成功",
            "data": {
                "total": total,
                "active": active
            }
        }
    except Exception as e:
        print(f"周边统计错误: {str(e)}")
        return {
            "code": 500,
            "message": f"周边统计失败: {str(e)}",
            "data": {}
        }

@coffeeProduct_api.get("/search", summary="咖啡产品搜索（支持多条件+分页）")
async def search_coffee_products(
        # 搜索参数（咖啡特有字段：origin_region产地、flavor_notes风味）
        keyword: Optional[str] = Query(None, description="搜索关键词（匹配名称、产地、风味）"),
        category_id: Optional[int] = Query(None, description="分类ID（传0表示不筛选）"),
        min_price: Optional[Decimal] = Query(None, ge=0, description="最低价格"),
        max_price: Optional[Decimal] = Query(None, ge=0, description="最高价格"),
        is_puton: Optional[str] = Query(None, description="是否上架（'true'/'1'为上架）"),  # 咖啡用is_puton
        is_recommend: Optional[str] = Query(None, description="是否推荐（'true'/'1'为推荐）"),  # 咖啡用is_recommend
        page: int = Query(1, ge=1, description="页码"),
        limit: int = Query(10, ge=1, le=100, description="每页数量")
):
    try:
        from app.models import CoffeeProduct  # 导入咖啡模型
        print("当前咖啡模型类：", CoffeeProduct)

        # 1. 转换布尔参数（与周边逻辑一致）
        def str_to_bool(val: Optional[str]) -> Optional[bool]:
            return val.lower() in ("true", "1") if val else None

        is_puton_bool = str_to_bool(is_puton)  # 咖啡上架状态
        is_recommend_bool = str_to_bool(is_recommend)  # 咖啡推荐状态

        # 2. 构建查询条件（适配咖啡模型字段）
        query = CoffeeProduct.all()
        filter_conditions = []

        # 关键词搜索（咖啡匹配：名称、产地、风味描述）
        if keyword:
            query = query.filter(
                Q(name__icontains=keyword) |  # 商品名称
                Q(origin_region__icontains=keyword) |  # 产地（咖啡特有）
                Q(flavor_notes__icontains=keyword)  # 风味描述（咖啡特有）
            )

        # 分类筛选（与周边逻辑一致）
        if category_id and category_id != 0:
            query = query.filter(category_id=category_id)

        # 价格范围筛选（与周边逻辑一致）
        if min_price:
            query = query.filter(price__gte=min_price)
        if max_price:
            query = query.filter(price__lte=max_price)

        # 咖啡特有状态筛选（上架is_puton、推荐is_recommend）
        if is_puton_bool is not None:
            query = query.filter(is_puton=is_puton_bool)
        if is_recommend_bool is not None:
            query = query.filter(is_recommend=is_recommend_bool)

        # 3. 分页逻辑（与周边完全一致）
        total = await query.count()
        offset = (page - 1) * limit
        results = await query.offset(offset).limit(limit).all()

        # 4. 返回结果（字段名用coffee_products，与周边的merchandise_products对应）
        return {
            "code": 200,
            "data": {
                "coffee_products": results,  # 咖啡列表字段名
                "pagination": {
                    "page": page,
                    "limit": limit,
                    "total": total,
                    "total_pages": (total + limit - 1) // limit if total > 0 else 0
                }
            }
        }

    except Exception as e:
        import traceback
        print(f"咖啡搜索错误详情：{str(e)}")
        print("错误堆栈：", traceback.format_exc())
        raise HTTPException(status_code=500, detail=f"咖啡搜索失败：{str(e)}")



@merchandiseProduct_api.get("/search", summary="周边商品搜索（支持多条件+分页）")
async def search_merchandise_products(
        keyword: Optional[str] = Query(None, description="搜索关键词（匹配商品名称、描述）"),
        category_id: Optional[int] = Query(None, description="分类ID（传0表示不筛选）"),
        min_price: Optional[Decimal] = Query(None, ge=0, description="最低价格"),
        max_price: Optional[Decimal] = Query(None, ge=0, description="最高价格"),
        is_active: Optional[str] = Query(None, description="是否上架（'true'/'1'为上架）"),
        is_featured: Optional[str] = Query(None, description="是否推荐（'true'/'1'为推荐）"),
        page: int = Query(1, ge=1, description="页码"),
        limit: int = Query(10, ge=1, le=100, description="每页数量")
):
    try:
        from app.models import MerchandiseProduct
        print("当前模型类：", MerchandiseProduct)  # 确认模型正确

        # 1. 转换布尔参数（保持不变）
        def str_to_bool(val: Optional[str]) -> Optional[bool]:
            return val.lower() in ("true", "1") if val else None

        is_active_bool = str_to_bool(is_active)
        is_featured_bool = str_to_bool(is_featured)

        # 2. 构建查询条件（Tortoise-ORM 语法）
        # 初始查询：获取所有商品
        query = MerchandiseProduct.all()

        # 关键词模糊搜索（用 Q 对象和 icontains）
        if keyword:
            # Q 对象实现 OR 逻辑，icontains 实现不区分大小写的模糊匹配
            query = query.filter(
                Q(name__icontains=keyword) | Q(description__icontains=keyword)
            )

        # 分类筛选（排除 0 值）
        if category_id and category_id != 0:
            # 直接用字段名筛选（Tortoise-ORM 支持直接传关键字参数）
            query = query.filter(category_id=category_id)

        # 价格范围筛选
        if min_price:
            query = query.filter(price__gte=min_price)  # gte 表示 >=
        if max_price:
            query = query.filter(price__lte=max_price)  # lte 表示 <=

        # 上架/推荐状态筛选
        if is_active_bool is not None:
            query = query.filter(is_active=is_active_bool)
        if is_featured_bool is not None:
            query = query.filter(is_featured=is_featured_bool)

        # 3. 分页逻辑（Tortoise-ORM 语法）
        total = await query.count()  # 总数量
        offset = (page - 1) * limit
        results = await query.offset(offset).limit(limit).all()  # 分页查询

        # 4. 返回结果
        return {
            "code": 200,
            "data": {
                "merchandise_products": results,
                "pagination": {
                    "page": page,
                    "limit": limit,
                    "total": total,
                    "total_pages": (total + limit - 1) // limit if total > 0 else 0
                }
            }
        }

    except Exception as e:
        # 打印详细错误（含堆栈信息），方便定位
        import traceback
        print(f"搜索错误详情：{str(e)}")
        print("错误堆栈：", traceback.format_exc())
        raise HTTPException(status_code=500, detail=f"搜索失败：{str(e)}")


@coffeeProduct_api.get("/",summary="获取咖啡产品列表")
async def get_coffee_products():
    """获取咖啡产品列表"""
    # 查询所有
    try:
        coffeeProducts = await CoffeeProduct.all()

        return {
            "code":200,
            "message": "查询成功",
            "data": coffeeProducts
        }
    except:
        return {
            "code": 404,
            "message": "查询失败"
        }

@merchandiseProduct_api.get("/",summary="获取周边商品列表")
async def get_merchandise_products():
    """获取周边商品列表"""
    # 查询所有
    try:
        merchandiseProducts = await MerchandiseProduct.all()

        return {
            "code":200,
            "message": "查询成功",
            "data": merchandiseProducts
        }
    except:
        return {
            "code": 404,
            "message": "查询失败"
        }


@coffeeProduct_api.get("/page", summary="根据分页获取咖啡产品列表")
async def get_coffee_products(
        page: int = Query(1, ge=1, description="页码，从1开始"),
        limit: int = Query(10, ge=1, le=100, description="每页数量，1-100之间"),
        sort_by: str = Query("created_at", description="排序字段，可选值: price, created_at"),
        sort_order: str = Query("desc", description="排序方向，可选值: asc, desc")
):
    """
    获取咖啡产品列表（带分页和排序）
    支持按价格(price)和上架时间(created_at)排序，默认按最新上架(created_at desc)排序
    """
    try:
        # 验证排序参数
        valid_sort_fields = ["price", "created_at"]
        valid_sort_orders = ["asc", "desc"]

        if sort_by not in valid_sort_fields:
            raise HTTPException(status_code=400, detail=f"无效的排序字段，可选值: {', '.join(valid_sort_fields)}")

        if sort_order not in valid_sort_orders:
            raise HTTPException(status_code=400, detail=f"无效的排序方向，可选值: {', '.join(valid_sort_orders)}")

        # 基础查询
        query = CoffeeProduct.all()

        # 应用排序，根据排序方向生成正确的排序表达式
        if sort_order == "asc":
            order_by_expr = sort_by
        else:  # desc
            order_by_expr = f"-{sort_by}"

        query = query.order_by(order_by_expr)

        # 获取总数（用于分页）
        total = await query.count()

        # 计算偏移量
        offset = (page - 1) * limit

        # 执行查询（带分页）
        coffee_products = await query.offset(offset).limit(limit).all()

        # 计算总页数
        total_pages = (total + limit - 1) // limit if total > 0 else 0

        # 返回响应
        return {
            "code": 200,
            "message": "查询成功",
            "data": {
                "coffee_products": coffee_products,
                "pagination": {
                    "page": page,
                    "limit": limit,
                    "total": total,
                    "total_pages": total_pages,
                    "has_next": page < total_pages,
                    "has_prev": page > 1,
                    "sort_by": sort_by,  # 返回当前排序字段
                    "sort_order": sort_order  # 返回当前排序方向
                }
            }
        }

    except Exception as e:
        # 记录错误日志（实际项目中建议使用 logger）
        print(f"获取咖啡产品列表失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}")

@coffeeProduct_api.get("/recommend", summary="获取推荐商品")
async def get_recommend_products():
    # 查看被推荐的咖啡商品
    coffee = await CoffeeProduct.filter(is_recommend=1).all()
    return {
        "code": 200,
        "message": "查询成功",
        "data": coffee,
    }



@merchandiseProduct_api.get("/page",summary="根据分页获取周边商品列表")
async def get_merchandise_products(
        page: int = Query(1, ge=1, description="页码，从1开始"),
        limit: int = Query(10, ge=1, le=100, description="每页数量，1-100之间"),
):
    """
    获取咖啡产品列表（带分页和排序）
    支持按价格(price)和上架时间(created_at)排序，默认按最新上架(created_at desc)排序
    """
    try:

        # 基础查询
        query = MerchandiseProduct.all()

        # 获取总数（用于分页）
        total = await query.count()

        # 计算偏移量
        offset = (page - 1) * limit

        # 执行查询（带分页）
        merchandise_products = await query.offset(offset).limit(limit).all()

        # 计算总页数
        total_pages = (total + limit - 1) // limit if total > 0 else 0

        # 返回响应
        return {
            "code": 200,
            "message": "查询成功",
            "data": {
                "merchandise_products": merchandise_products,
                "pagination": {
                    "page": page,
                    "limit": limit,
                    "total": total,
                    "total_pages": total_pages,
                    "has_next": page < total_pages,
                    "has_prev": page > 1,
                }
            }
        }

    except Exception as e:
        # 记录错误日志（实际项目中建议使用 logger）
        print(f"获取咖啡周边产品列表失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}")

@coffeeProduct_api.get("/{id}",summary="获取单个咖啡产品")
async def get_coffee_product(id: int):
    """获取咖啡产品详情"""
    try:
        coffee_product = await CoffeeProduct.get(id=id)
        return {
            "code": 200,
            "message": "查询成功",
            "data": coffee_product
        }
    except:
        return {
            "code": 404,
            "message": "查询失败"
        }



@merchandiseProduct_api.get("/{id}",summary="获取一个周边商品详情")
async def get_merchandise_product(id: int):
    """获取周边商品详情"""
    try:
        merchandise_product = await MerchandiseProduct.get(id=id)
        return {
            "code": 200,
            "message": "查询成功",
            "data": merchandise_product
        }
    except:
        return {
            "code": 404,
            "message": "查询失败"
        }

class CoffeeProductIn(BaseModel):
    name: str = Field(..., max_length=100, description="产品名称")
    category_id: int = Field(..., description="所属分类ID")
    origin_region: str = Field(..., max_length=100, description="产区")
    flavor_notes: Optional[str] = Field(None, description="风味描述")
    description: Optional[str] = Field(None, description="详细介绍")
    price: Decimal = Field(..., ge=0, max_digits=10, decimal_places=2, description="售价")
    original_price: Optional[Decimal] = Field(None, ge=0, max_digits=10, decimal_places=2, description="原价")
    stock: int = Field(0, ge=0, description="库存")
    weight: Optional[Decimal] = Field(None, ge=0, max_digits=6, decimal_places=2, description="净重(g)")
    image_urls: Optional[List[str]] = Field(None, description="商品图片URL列表")
    is_recommend: bool = Field(False, description="是否推荐")
    is_puton: bool = Field(True, description="是否上架")

    @validator("price", "original_price", "weight", pre=True)
    def normalize_decimal(cls, v):
        if v is None:
            return v
        return Decimal(str(v))

class CoffeeProductOut(BaseModel):
    id: int
    name: str
    category_id: int
    origin_region: str
    flavor_notes: Optional[str]
    description: Optional[str]
    price: Decimal
    original_price: Optional[Decimal]
    stock: int
    weight: Optional[Decimal]
    image_urls: Optional[List[str]]
    is_recommend: bool
    is_puton: bool
    sales_count: int
    view_count: int
    created_at: datetime
    updated_at: datetime

    class Config:
        from_attributes = True

@coffeeProduct_api.post("/", summary="创建咖啡产品",response_model=CoffeeProductOut)
async def create_coffee_product(product_in: CoffeeProductIn):
    """创建咖啡产品"""
    # 可选：检查分类是否存在
    if not await CoffeeCategory.filter(id=product_in.category_id).exists():
        raise HTTPException(status_code=404, detail="分类不存在")

    product = await CoffeeProduct.create(
        name=product_in.name,
        category_id=product_in.category_id,
        origin_region=product_in.origin_region,
        flavor_notes=product_in.flavor_notes,
        description=product_in.description,
        price=product_in.price,
        original_price=product_in.original_price,
        stock=product_in.stock,
        weight=product_in.weight,
        image_urls=product_in.image_urls,
        is_recommend=product_in.is_recommend,
        is_puton=product_in.is_puton,
    )

    # 拉取外键对象（如果前端需要 category 详情）
    await product.fetch_related("category")
    return {
        "code": 200,
        "message": "添加咖啡商品成功",
        "data": product
    }

class MerchandiseProductIn(BaseModel):
    name: str = Field(..., max_length=100, description="商品名称")
    category_id: int = Field(..., description="分类ID")
    description: Optional[str] = Field(None, description="商品描述")
    price: Decimal = Field(..., ge=0, max_digits=10, decimal_places=2, description="价格")
    original_price: Optional[Decimal] = Field(None, ge=0, max_digits=10, decimal_places=2, description="原价")
    stock: int = Field(0, ge=0, description="库存数量")
    image_urls: Optional[List[str]] = Field(None, description="图片URL数组")
    is_featured: bool = Field(False, description="是否推荐")
    is_active: bool = Field(True, description="是否上架")

    @validator("price", "original_price", pre=True)
    def to_decimal(cls, v):
        if v is None:
            return v
        return Decimal(str(v))

class MerchandiseProductOut(BaseModel):
    id: int
    name: str
    category_id: int
    description: Optional[str]
    price: Decimal
    original_price: Optional[Decimal]
    stock: int
    image_urls: Optional[List[str]]
    is_featured: bool
    is_active: bool
    sales_count: int
    view_count: int
    created_at: datetime
    updated_at: datetime

    class Config:
        from_attributes = True

@merchandiseProduct_api.post("/",summary="创建周边商品",response_model=MerchandiseProductOut)
async def create_merchandise_product(product_in: MerchandiseProductIn):
    if not await MerchandiseCategory.filter(id=product_in.category_id).exists():
        raise HTTPException(status_code=404, detail="周边分类不存在")

    product = await MerchandiseProduct.create(
        name=product_in.name,
        category_id=product_in.category_id,
        description=product_in.description,
        price=product_in.price,
        original_price=product_in.original_price,
        stock=product_in.stock,
        image_urls=product_in.image_urls,
        is_featured=product_in.is_featured,
        is_active=product_in.is_active,
    )

    await product.fetch_related("category")
    return {
        "code": 200,
        "message": "添加周边商品成功",
        "data": product
    }

@merchandiseProduct_api.post("/{id}/images",summary="上传周边商品图片")
async def upload_merchandise_product_images(id: int,
    files: List[UploadFile] = File(...)):
    """上传周边产品图片"""
    # 检查产品是否存在
    product = await MerchandiseProduct.filter(id=id).first()
    if not product:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="周边商品不存在"
        )

    # 保存上传的图片
    uploaded_images = []
    for file in files:
        try:
            # 使用通用函数保存文件
            file_info = await save_upload_file(file, "merchandise_products")
            uploaded_images.append(file_info)
        except HTTPException as e:
            # 如果某个文件上传失败，继续处理其他文件
            continue

    # 更新产品的图片URL列表
    current_images = product.image_urls or []
    new_image_paths = [img["file_path"] for img in uploaded_images]
    updated_images = current_images + new_image_paths

    # 更新数据库
    await MerchandiseProduct.filter(id=id).update(image_urls=updated_images)

    # 重新获取产品信息
    product = await MerchandiseProduct.filter(id=id).first()

    return {
        "code": 200,
        "message": f"成功上传 {len(uploaded_images)} 张图片",
        "uploaded_images": uploaded_images,
        "all_images": product.image_urls
    }

@coffeeProduct_api.post("/{id}/images",summary="上传咖啡产品图片")
async def upload_coffee_product_images(id: int,
    files: List[UploadFile] = File(...)):
    """上传咖啡产品图片"""
    # 检查产品是否存在
    product = await CoffeeProduct.filter(id=id).first()
    if not product:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="咖啡产品不存在"
        )

    # 保存上传的图片
    uploaded_images = []
    for file in files:
        try:
            # 使用通用函数保存文件
            file_info = await save_upload_file(file, "coffee_products")
            uploaded_images.append(file_info)
        except HTTPException as e:
            # 如果某个文件上传失败，继续处理其他文件
            continue

    # 更新产品的图片URL列表
    current_images = product.image_urls or []
    new_image_paths = [img["file_path"] for img in uploaded_images]
    updated_images = current_images + new_image_paths

    # 更新数据库
    await CoffeeProduct.filter(id=id).update(image_urls=updated_images)

    # 重新获取产品信息
    product = await CoffeeProduct.filter(id=id).first()

    return {
        "code": 200,
        "message": f"成功上传 {len(uploaded_images)} 张图片",
        "uploaded_images": uploaded_images,
        "all_images": product.image_urls
    }


@coffeeProduct_api.delete("/{id}/images/{image_index}",summary="删除咖啡产品图片")
async def delete_coffee_product_image(
        id: int,
        image_index: int
):
    """
    删除咖啡产品的指定图片
    """
    # 检查产品是否存在
    product = await CoffeeProduct.filter(id=id).first()
    if not product:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="咖啡产品不存在"
        )

    # 检查图片列表是否存在
    if not product.image_urls or len(product.image_urls) <= image_index:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="图片不存在"
        )

    # 获取要删除的图片路径
    image_path = product.image_urls[image_index]

    # 从文件系统中删除图片
    try:
        await delete_file(image_path)
    except Exception as e:
        # 如果文件删除失败，记录错误但继续执行
        print(f"删除文件失败: {e}")

    # 从图片列表中移除该图片
    updated_images = product.image_urls.copy()
    updated_images.pop(image_index)

    # 更新数据库
    await CoffeeProduct.filter(id=id).update(image_urls=updated_images)

    # 重新获取产品信息
    product = await CoffeeProduct.filter(id=id).first()

    return {
        "code": 200,
        "message": "图片删除成功",
        "remaining_images": product.image_urls
    }

@merchandiseProduct_api.delete("/{id}/images/{image_index}",summary="删除周边商品图片")
async def delete_merchandise_product_image(
        id: int,
        image_index: int,
):
    """
    删除咖啡产品的指定图片
    """
    # 检查产品是否存在
    product = await MerchandiseProduct.filter(id=id).first()
    if not product:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="该周边商品不存在"
        )

    # 检查图片列表是否存在

    if not product.image_urls or len(product.image_urls) <= image_index:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="图片不存在"
        )

    # 获取要删除的图片路径
    image_path = product.image_urls[image_index]

    # 从文件系统中删除图片
    try:
        await delete_file(image_path)
    except Exception as e:
        # 如果文件删除失败，记录错误但继续执行
        print(f"删除文件失败: {e}")

    # 从图片列表中移除该图片
    updated_images = product.image_urls.copy()
    updated_images.pop(image_index)

    # 更新数据库
    await MerchandiseProduct.filter(id=id).update(image_urls=updated_images)

    # 重新获取产品信息
    product = await MerchandiseProduct.filter(id=id).first()

    return {
        "code": 200,
        "message": "图片删除成功",
        "remaining_images": product.image_urls
    }

class MerchandiseProductUpdate(BaseModel):
    name: Optional[str] = Field(None, max_length=100)
    category_id: Optional[int] = None
    description: Optional[str] = None
    price: Optional[Decimal] = Field(None, ge=0, max_digits=10, decimal_places=2)
    original_price: Optional[Decimal] = Field(None, ge=0, max_digits=10, decimal_places=2)
    stock: Optional[int] = Field(None, ge=0)
    image_urls: Optional[List[str]] = None
    is_featured: Optional[bool] = None
    is_active: Optional[bool] = None

    @validator("price", "original_price", pre=True)
    def to_decimal(cls, v):
        if v is None:
            return v
        return Decimal(str(v))


@merchandiseProduct_api.put(
    "/{product_id}",
    summary="更新周边商品",
    response_model=MerchandiseProductOut
)
async def update_merchandise_product(
    product_id: int,
    payload: MerchandiseProductUpdate
):
    product = await MerchandiseProduct.get_or_none(id=product_id)
    if not product:
        raise HTTPException(status_code=404, detail="周边商品不存在")

    if payload.category_id is not None:
        if not await MerchandiseCategory.filter(id=payload.category_id).exists():
            raise HTTPException(status_code=404, detail="周边分类不存在")

    update_data = payload.dict(exclude_unset=True)
    await product.update_from_dict(update_data).save()
    await product.refresh_from_db()
    await product.fetch_related("category")
    return {
        "code": 200,
        "message": "周边商品修改成功",
        "data": product
    }

class CoffeeProductUpdate(BaseModel):
    name: Optional[str] = Field(None, max_length=100)
    category_id: Optional[int] = None
    origin_region: Optional[str] = Field(None, max_length=100)
    flavor_notes: Optional[str] = None
    description: Optional[str] = None
    price: Optional[Decimal] = Field(None, ge=0, max_digits=10, decimal_places=2)
    original_price: Optional[Decimal] = Field(None, ge=0, max_digits=10, decimal_places=2)
    stock: Optional[int] = Field(None, ge=0)
    weight: Optional[Decimal] = Field(None, ge=0, max_digits=6, decimal_places=2)
    image_urls: Optional[List[str]] = None
    is_recommend: Optional[bool] = None
    is_puton: Optional[bool] = None

    @validator("price", "original_price", "weight", pre=True)
    def normalize_decimal(cls, v):
        if v is None:
            return v
        return Decimal(str(v))

@coffeeProduct_api.put(
    "/{product_id}",
    summary="更新咖啡产品",
    response_model=CoffeeProductOut
)
async def update_coffee_product(
    product_id: int,
    payload: CoffeeProductUpdate
):
    product = await CoffeeProduct.get_or_none(id=product_id)
    if not product:
        raise HTTPException(status_code=404, detail="咖啡产品不存在")

    # 如果修改了分类，验证新分类是否存在
    if payload.category_id is not None:
        if not await CoffeeCategory.filter(id=payload.category_id).exists():
            raise HTTPException(status_code=404, detail="分类不存在")

    # 仅更新非 None 的字段
    update_data = payload.dict(exclude_unset=True)
    await product.update_from_dict(update_data).save()
    await product.refresh_from_db()
    await product.fetch_related("category")
    return {
        "code": 200,
        "message": "咖啡产品修改成功",
        "data": product
    }
@coffeeProduct_api.delete(
    "/{product_id}",
    summary="删除咖啡产品",
    status_code=204  # No Content
)
async def delete_coffee_product(product_id: int):
    product = await CoffeeProduct.get_or_none(id=product_id)
    if not product:
        raise HTTPException(status_code=404, detail="咖啡产品不存在")

    await product.delete()
    return {
        "code": 200,
        "message": "删除成功"
    }
@merchandiseProduct_api.delete(
    "/{product_id}",
    summary="删除周边商品",
    status_code=204  # No Content
)
async def delete_merchandise_product(product_id: int):
    product = await MerchandiseProduct.get_or_none(id=product_id)
    if not product:
        raise HTTPException(status_code=404, detail="周边商品不存在")

    await product.delete()
    return {
        "code": 200,
        "message": "删除成功"
    }


@coffeeProduct_api.get("/by_category/{category_id}", summary="通过分类ID获取咖啡产品")
async def get_coffee_products_by_category(category_id: int):
    """通过分类ID获取该分类下的所有咖啡产品"""
    try:
        # 验证分类是否存在
        if not await CoffeeCategory.filter(id=category_id).exists():
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="分类不存在"
            )

        # 查询该分类下的所有产品
        coffee_products = await CoffeeProduct.filter(category_id=category_id).all()

        return {
            "message": "查询成功",
            "code":200,
            "data": coffee_products
        }
    except HTTPException:
        raise
    except Exception:
        return {
            "code": 404,
            "message": "查询失败"
        }


@merchandiseProduct_api.get("/by_category/{category_id}", summary="通过分类ID获取周边商品")
async def get_merchandise_products_by_category(category_id: int):
    """通过分类ID获取该分类下的所有周边商品"""
    try:
        # 验证分类是否存在
        if not await MerchandiseCategory.filter(id=category_id).exists():
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="周边分类不存在"
            )

        # 查询该分类下的所有商品
        merchandise_products = await MerchandiseProduct.filter(category_id=category_id).all()

        return {
            "code":200,
            "message": "查询成功",
            "data": merchandise_products
        }
    except HTTPException:
        raise
    except Exception:
        return {
            "code": 404,
            "message": "查询失败"
        }







